/**
 *       Copyright (c) 2012 Lukas Zaruba
 *
 *   This file is part of Robot Playground.
 *
 *   Robot Playground is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Robot Playground is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with Robot Playground. If not, see <http://www.gnu.org/licenses/>.
 */
package net.zarubsys.robotplayground.joystick;

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.BitSet;

import net.zarubsys.commons.utils.LoggingUncaughtExceptionHandler;

import org.apache.log4j.Logger;

/**
 * JoystickNativeAccessor
 *
 * @author Lukas Zaruba, lukas.zaruba@gmail.com
 */
public class JoystickNativeAccessor implements IJoystickEventsCallback {
	
	private static final Logger LOG = Logger.getLogger(JoystickNativeAccessor.class);
	private static final String LIBRARY_NAME = "robotPlaygroundJoystick";
	private static final UncaughtExceptionHandler EX_HANDLER = new LoggingUncaughtExceptionHandler();
	
	private String device;
	private int fileDescriptor = -1;
	private int x;
	private int y;
	private BitSet buttons = new BitSet(12); // supports only 12 buttons
	

	/*package*/ JoystickNativeAccessor(String device) {
		this.device = device;
		openDevice();
		if (fileDescriptor == -1) return;
		Thread t = new Thread(new JoystickEventsMonitorRunnable(this));
		t.setName("Joystick - " + device);
		t.setUncaughtExceptionHandler(EX_HANDLER);
		t.start();
	}
	
	private void openDevice() {
		fileDescriptor = openJoystick(device);
		if (fileDescriptor == -1) {
			LOG.error("Couldn't open device " + device);
		}
	}
	
	public String readJoystickSignature() {
		if (fileDescriptor == -1) {
			throw new IllegalStateException("Device " + device + " is not opened, cannot read signature.");
		}
		return readSignature(fileDescriptor);
	}

	public void close() {
		if (fileDescriptor == -1) {
			throw new IllegalStateException("Device " + device + " is not opened, cannot close it.");
		}
		closeJoystick(fileDescriptor);
		fileDescriptor = -1;
		device = null;
	}
	
	public int getX() {
		return x;
	}
	
	public int getY() {
		return y;
	}
	
	public boolean getButton(int buttonId) {
		if (buttonId > 11) {
			throw new IllegalArgumentException("There is only 12 supported buttons!");
		}
		return buttons.get(buttonId);
	}
	
	public boolean isAccessible() {
		return fileDescriptor != -1;
	}
	
	public void readEventsFromDevice(IJoystickEventsCallback callback) {
		if (fileDescriptor == -1) {
			throw new IllegalStateException("Device " + device + " is not opened, cannot read from it.");
		}
		readEvents(fileDescriptor, callback);
	}
	
	/*package*/ native int openJoystick(String device);
	/*package*/ native void closeJoystick(int fd);
	/*package*/ native String readSignature(int fd);
	/*package*/ native void readEvents(int fd, IJoystickEventsCallback callback);
	
	static {
		LOG.info("Loading shared library \"" + LIBRARY_NAME + "\"");
		try {
			System.loadLibrary(LIBRARY_NAME);
		} catch (UnsatisfiedLinkError e) {
			LOG.error("Loading shared library \"" + LIBRARY_NAME + "\" failed.", e);
		}
	}

	@Override
	public void callback(int type, int number, int value) {
//		System.out.println(type + ", " + number + ", " + value);
		if (type == 1) { // button
			if (number > 11) return;
			buttons.set(number, value == 0 ? false : true);
		} else if (type == 2) { // axis
			if (number == 0) {
				x = value;
			} else if (number == 1) {
				y = value;
			}
		}
	}

	@Override
	public boolean doClose() {
		return device == null;
	}
	
	private static class JoystickEventsMonitorRunnable implements Runnable {
		
		private final JoystickNativeAccessor accessor;
		
		public JoystickEventsMonitorRunnable(JoystickNativeAccessor accessor) {
			this.accessor = accessor;
		}

		@Override
		public void run() {
			accessor.readEventsFromDevice(accessor);
		}
		
	}
	
}

